<!--
Copyright (c) 2023 The Khronos Group Inc.
Use of this source code is governed by an MIT-style license that can be
found in the LICENSE.txt file.
-->

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebGL WEBGL_stencil_texturing Conformance Tests</title>
<link rel="stylesheet" href="../../resources/js-test-style.css"/>
<script src="../../js/js-test-pre.js"></script>
<script src="../../js/webgl-test-utils.js"></script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>
<script>
"use strict";
description("This test verifies the functionality of the WEBGL_stencil_texturing extension, if it is available.");

debug("");

var wtu = WebGLTestUtils;
var gl = wtu.create3DContext(null, null, 2);
var ext;

function runTestNoExtension() {
    debug("");
    debug("Check the texture parameter without the extension");

    const tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);

    shouldBeNull("gl.getTexParameter(gl.TEXTURE_2D, 0x90EA /* DEPTH_STENCIL_TEXTURE_MODE_WEBGL */)");
    wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");

    gl.texParameteri(gl.TEXTURE_2D, 0x90EA /* DEPTH_STENCIL_TEXTURE_MODE_WEBGL */, gl.DEPTH_COMPONENT);
    wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for texParameteri without enabling the extension");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");

    gl.texParameterf(gl.TEXTURE_2D, 0x90EA /* DEPTH_STENCIL_TEXTURE_MODE_WEBGL */, gl.DEPTH_COMPONENT);
    wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for texParameterf without enabling the extension");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");

    const sampler = gl.createSampler();
    gl.samplerParameteri(sampler, 0x90EA /* DEPTH_STENCIL_TEXTURE_MODE_WEBGL */, gl.DEPTH_COMPONENT);
    wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for samplerParameteri");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");
    gl.samplerParameterf(sampler, 0x90EA /* DEPTH_STENCIL_TEXTURE_MODE_WEBGL */, gl.DEPTH_COMPONENT);
    wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for samplerParameterf");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");
}

function checkEnums() {
    debug("");
    debug("Check enums");
    shouldBe("ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL", "0x90EA");
    shouldBe("ext.STENCIL_INDEX_WEBGL",              "0x1901");
}

function checkQueries() {
    const tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);

    debug("");
    debug("Check default texture state");
    shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'gl.DEPTH_COMPONENT');
    debug("");
    debug("Check texture state updates using texParameteri");
    gl.texParameteri(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, ext.STENCIL_INDEX_WEBGL);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
    shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'ext.STENCIL_INDEX_WEBGL');
    gl.texParameteri(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, gl.DEPTH_COMPONENT);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
    shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'gl.DEPTH_COMPONENT');
    gl.texParameteri(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, 0);
    wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "invalid depth stencil mode value rejected by texParameteri");
    shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'gl.DEPTH_COMPONENT');
    debug("");
    debug("Check texture state updates using texParameterf");
    gl.texParameterf(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, ext.STENCIL_INDEX_WEBGL);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
    shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'ext.STENCIL_INDEX_WEBGL');
    gl.texParameterf(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, gl.DEPTH_COMPONENT);
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
    shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'gl.DEPTH_COMPONENT');
    gl.texParameterf(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, 0);
    wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "invalid depth stencil mode value rejected by texParameterf");
    shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'gl.DEPTH_COMPONENT');

    debug("");
    debug("Check that depth stencil texture mode is not accepted as a sampler state");
    const sampler = gl.createSampler();
    gl.samplerParameteri(sampler, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, gl.DEPTH_COMPONENT);
    wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for samplerParameteri");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");
    gl.samplerParameterf(sampler, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, gl.DEPTH_COMPONENT);
    wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for samplerParameterf");
    wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");
}

function checkSampling() {
    const formats = [
        {name: "DEPTH_COMPONENT16", internalFormat: gl.DEPTH_COMPONENT16,
         format: gl.DEPTH_COMPONENT, type: gl.UNSIGNED_SHORT},
        {name: "DEPTH_COMPONENT24", internalFormat: gl.DEPTH_COMPONENT24,
         format: gl.DEPTH_COMPONENT, type: gl.UNSIGNED_INT},
        {name: "DEPTH_COMPONENT32F", internalFormat: gl.DEPTH_COMPONENT32F,
         format: gl.DEPTH_COMPONENT, type: gl.FLOAT},
        {name: "DEPTH24_STENCIL8", internalFormat: gl.DEPTH24_STENCIL8,
         format: gl.DEPTH_STENCIL, type: gl.UNSIGNED_INT_24_8},
        {name: "DEPTH32F_STENCIL8", internalFormat: gl.DEPTH32F_STENCIL8,
         format: gl.DEPTH_STENCIL, type: gl.FLOAT_32_UNSIGNED_INT_24_8_REV}
    ];

    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.STENCIL_TEST);
    gl.stencilFunc(gl.ALWAYS, 170, 0xFF);
    gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);

    wtu.setupUnitQuad(gl);

    const drawProgram = wtu.setupProgram(gl, [wtu.simpleVertexShader,
                                              wtu.simpleColorFragmentShader]);

    const readDepthProgram = wtu.setupProgram(gl, [wtu.simpleTextureVertexShaderESSL300,
                                                   wtu.simpleTextureFragmentShaderESSL300]);

    const readStencilShader = `#version 300 es
        precision highp float;
        uniform highp usampler2D tex;
        in vec2 texCoord;
        out vec4 out_color;
        void main() {
            out_color = vec4(texture(tex, texCoord)) / 255.0;
        }`;
    const readStencilProgram = wtu.setupProgram(gl, [wtu.simpleTextureVertexShaderESSL300,
                                                     readStencilShader]);

    for (const format of formats) {
        debug("");
        debug(`Testing depth stencil texture modes with ${format.name}`);
        const fbo = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

        const rbo = gl.createRenderbuffer();
        gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
        gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, 1, 1);
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);

        const tex = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, tex);
        gl.texImage2D(gl.TEXTURE_2D, 0, format.internalFormat, 1, 1, 0, format.format, format.type, null);
        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texture created");

        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, tex, 0);
        if (format.format == gl.DEPTH_STENCIL) {
            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.TEXTURE_2D, tex, 0);
        }
        wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);

        gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
        gl.useProgram(drawProgram);
        wtu.drawUnitQuad(gl);
        wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors after drawing to the depth or depth stencil texture");

        // Detach the depth or depth stencil texture to avoid feedback loop
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, null);
        wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);

        const magFilters = ['NEAREST', 'LINEAR'];

        const minFilters = [
            'NEAREST',
            'LINEAR',
            'NEAREST_MIPMAP_NEAREST',
            'LINEAR_MIPMAP_NEAREST',
            'NEAREST_MIPMAP_LINEAR',
            'LINEAR_MIPMAP_LINEAR'
        ];

        const modes = [
            [gl.DEPTH_COMPONENT, 'DEPTH_COMPONENT'],
            [ext.STENCIL_INDEX_WEBGL, 'STENCIL_INDEX_WEBGL']
        ];

        const programs = [
            [readDepthProgram, 'depth'],
            [readStencilProgram, 'stencil']
        ];

        function validFilters(magFilter, minFilter) {
            return magFilter == gl.NEAREST &&
                   (minFilter == gl.NEAREST || minFilter == gl.NEAREST_MIPMAP_NEAREST);
        }

        for (const program of programs) {
            gl.useProgram(program[0]);
            for (const mode of modes) {
                gl.texParameteri(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, mode[0]);
                for (const magFilter of magFilters) {
                    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl[magFilter]);
                    for (const minFilter of minFilters) {
                        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl[minFilter]);
                        debug(`Program: ${program[1]}, mode: ${mode[1]}, mag: ${magFilter}, min: ${minFilter}`);

                        gl.clear(gl.COLOR_BUFFER_BIT);
                        wtu.drawUnitQuad(gl);

                        if (format.format == gl.DEPTH_COMPONENT || mode[0] == gl.DEPTH_COMPONENT) {
                            if (program[1] == 'depth') {
                                wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
                                if (validFilters(gl[magFilter], gl[minFilter])) {
                                    wtu.checkCanvasRect(gl, 0, 0, 1, 1, [128, 0, 0, 255], "sampling depth from complete texture", 1);
                                } else {
                                    wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0, 0, 0, 255], "sampling depth from incomplete texture", 1);
                                }
                            } else {
                                wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "sampling depth using incompatible program");
                            }
                        } else {
                            if (program[1] == 'stencil') {
                                wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
                                if (validFilters(gl[magFilter], gl[minFilter])) {
                                    wtu.checkCanvasRect(gl, 0, 0, 1, 1, [170, 0, 0, 1], "sampling stencil from complete texture", 1);
                                } else {
                                    // Incomplete textures may produce [0, 0, 0, 1] or [0, 0, 0, 255].
                                    const value = new Uint8Array(4);
                                    gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, value);
                                    wtu.glErrorShouldBe(gl, gl.NO_ERROR);
                                    const msg = "sampling stencil from incomplete texture";
                                    if (value[0] == 0 && value[1] == 0 && value[2] == 0 && (value[3] == 1 || value[3] == 255)) {
                                        testPassed(msg);
                                    } else {
                                        testFailed(`${msg}: ${value}`);
                                    }
                                }
                            } else {
                                wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "sampling stencil using incompatible program");
                            }
                        }
                    }
                }
            }
        }
    }
}

function runTest() {
    if (!gl) {
        testFailed("context does not exist");
        return;
    }
    testPassed("context exists");

    runTestNoExtension();

    ext = gl.getExtension("WEBGL_stencil_texturing");
    wtu.runExtensionSupportedTest(gl, "WEBGL_stencil_texturing", ext !== null);

    if (ext !== null) {
        checkEnums();
        checkQueries();
        checkSampling();
    } else {
        testPassed("No WEBGL_stencil_texturing support -- this is legal");
    }
}

runTest();

var successfullyParsed = true;
</script>
<script src="../../js/js-test-post.js"></script>
</body>
</html>
